
-- Vector math class with operators

require("class")

local vector_meta
local function is_vector(v) return type(v)=="vector" end
local function vector_check(v, n, name, argn)
	if not is_vector(v) then error("vector "..name..": argument #"..(argn or 1)..": expected vector, got "..type(v), n+1) end
end
local function vector_checksamelen(v1, v2, name)
	vector_check(v1, 3, name, 1)
	vector_check(v2, 3, name, 2)
	if #v1~=#v2 then error("vector "..name..": vector lengths do not match (lengths are "..#v1.." and "..#v2..")", 3) end
	return #v1
end
local function vector_opnnn(name, op)
	return function(v1, v2)
		local len = vector_checksamelen(v1, v2, name)
		local v3 = {}
		for i = 1, len do
			v3[i] = op(v1[i], v2[i])
		end
		return vector(v3)
	end
end
local function vector_opnxn(name, op)
	return function(v1, v2)
		if is_vector(v2) then
			local len = vector_checksamelen(v1, v2, name)
			local v3 = {}
			for i = 1, len do
				v3[i] = op(v1[i], v2[i])
			end
			return vector(v3)
		else
			vector_check(v1, 3, name)
			local len = #v1
			local v3 = {}
			for i = 1, len do
				v3[i] = op(v1[i], v2)
			end
			return vector(v3)
		end
	end
end
local function vector_opn0n(name, op)
	return function(v1)
		vector_check(v1, 1, name)
		local len = #v1
		local v2 = {}
		for i = 1, len do
			v2[i] = op(v1[i])
		end
		return vector(v2)
	end
end
local vector_indices = {x = 1, y = 2, z = 3, w = 4, r = 1, g = 2, b = 3, a = 4}
local vector_indices1 = {"x", "y", "z", "w"}
local vector_indices2 = {"r", "g", "b", "a"}
local vector_class = class("vector", {
	__index = function(t, k)
		if tonumber(k) then return rawget(t, k)
		elseif vector_indices[k] then return rawget(t, vector_indices[k])
		else return getmetatable(t)[k]
		end
	end,
	__newindex = function(t, k, v)
		if tonumber(k) then rawset(t, k, v)
		elseif vector_indices[k] then rawset(t, vector_indices[k], v)
		else return
		end
	end,
	__add = vector_opnnn("add", function(x1, x2) return x1+x2 end),
	__sub = vector_opnnn("sub", function(x1, x2) return x1-x2 end),
	__mul = vector_opnxn("mul", function(x1, x2) return x1*x2 end),
	__div = vector_opnxn("div", function(x1, x2) return x1/x2 end),
	__pow = vector_opnxn("pow", function(x1, x2) return x1^x2 end),
	__unm = vector_opn0n("inv", function(x1) return -x1 end),
	__concat = nil,
	--__len = function(v1) return #v1 end,
	__len = nil,
	__eq = function(v1, v2)
		local len = vector_checksamelen(v1, v2, "equals")
		for i = 1, len do
			if v1[i]~=v2[i] then return false end
		end
		return true
	end,
	__lt = nil,
	__le = nil,
	__call = nil,
	abs = vector_opn0n("abs", math.abs),
	length = function(v1)
		vector_check(v1, 2, "length")
		local len = #v1
		local l = 0
		for i = 1, len do
			l = l + v1[i]^2
		end
		return math.sqrt(l)
	end,
	normalize = function(v1)
		vector_check(v1, 2, "normal")
		local length = v1:length()
		local len = #v1
		local v3 = {}
		for i = 1, len do
			v3[i] = v1[i]/length
		end
		return vector(v3)
	end,
	__tostring = function(v1)
		vector_check(v1, 2, "tostring")
		local st = {}
		local len = #v1
		for i = 1, len do
			table.insert(st, tostring(v1[i]))
		end
		return "{ "..table.concat(st, ", ").." }"
	end,
	list = function(v1) return unpack(v1) end,
	floor = vector_opn0n("floor", function(x1) return math.floor(x1) end),
	ceil  = vector_opn0n("ceil" , function(x1) return math.ceil (x1) end),
	round = vector_opn0n("round", function(x1) return math.round(x1) end),
	dot = function(v1, v2)
		local len = vector_checksamelen(v1, v2, "dot")
		local x = 0
		for i = 1, len do
			x = x + v1[i]*v2[i]
		end
		return x
	end,
	cross = function(v1, v2)
		local len = vector_checksamelen(v1, v2, "cross")
		if len ~= 3 then error("cross product: vector length is not 3 (length is "..len..")") end
		return vector{
			v1[2]*v2[3] - v1[3]*v2[2],
			v1[3]*v2[1] - v1[1]*v2[3],
			v1[1]*v2[2] - v1[2]*v2[1],
		}
	end,
	rotate = function(v1, r)
		vector_check(v1, 2, "rotate")
		if type(r)~="number" then error("vecto rotate: Invalid rotation "..tostring(rot), 2) end
		r = r%4
		local v2
		if     r==0 then v2 = vector{  v1[1],  v1[2], v1[3] }
		elseif r==1 then v2 = vector{  v1[2], -v1[1], v1[3] }
		elseif r==2 then v2 = vector{ -v1[1], -v1[2], v1[3] }
		elseif r==3 then v2 = vector{ -v1[2],  v1[1], v1[3] }
		else error("vector.rotate: invalid rotation "..rot, 2) end
		return v2
	end,
	rotateAbs = function(v1, r)
		vector_check(v1, 2, "rotateAbs")
		if type(r)~="number" then error("vector rotateAbs: Invalid rotation "..tostring(rot), 2) end
		r = r%2
		local v2
		if     r==0 then v2 = vector{ v1[1], v1[2], v1[3] }
		elseif r==1 then v2 = vector{ v1[2], v1[1], v1[3] }
		else error("vector.rotateAbs: invalid rotation "..rot, 2) end
		return v2
	end,
	tsString = function(v)
		vector_check(v, 2, "tsString")
		return table.concat(v, " ")
	end,
	distanceTo = function(v1, v2)
		vector_checksamelen(v1, v2, "distanceTo")
		return (v1 - v2):length()
	end,
})
function vector(vi)
	if vi then
		if type(vi)=="string" then
			local vi2 = {}
			for val in vi:gmatch("[0-9%.%-e]+") do
				table.insert(vi2, tonumber(val))
			end
			vi = vi2
		elseif type(vi)~="table" and type(vi)~="vector" then
			error("vector: argument #1: expected input table, got "..type(vi), 2)
		end
		local v = {}
		if #vi>0 then
			for i = 1, #vi do v[i] = vi[i] end
		else
			for i, n in ipairs(vector_indices1) do v[i] = vi[n] end
			if #v==0 then
				for i, n in ipairs(vector_indices2) do v[i] = vi[n] end
				if #v==0 then
					error("vector: argument #1: table contains no values", 2)
				end
			end
		end
		return vector_class(v)
	else
		error("vector: argument #1: expected input table, got nil", 2)
	end
end
